package com.xj.opcua.client.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import javax.xml.bind.DatatypeConverter;

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.sdk.client.model.nodes.objects.BaseObjectTypeNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaObjectNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.Stack;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
import org.eclipse.milo.opcua.stack.core.types.enumerated.IdType;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.eclipse.milo.opcua.stack.core.types.structured.ReferenceDescription;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;
import com.xj.opcua.client.Client;
import com.xj.opcua.client.manager.OpcUaClientManager;
import com.xj.opcua.data.cache.OpcUaDataCache;
import com.xj.opcua.util.StringUtils;

public class OpcUaClentUtils {
	
	private final static Logger logger = LoggerFactory.getLogger(OpcUaClentUtils.class);
	
	/**
	 * 	获取连接的客户端
	 * @param url
	 * @return
	 */
	public static OpcUaClient getOpcUaClent(String url) {
		if(Client.map.containsKey(url)) {
			List<Object> list = Client.map.get(url);
			if(list!=null & list.size()>0) {
				return (OpcUaClient) list.get(0);
			}	
		}
		return null;
	}
	
	
	/**
	 * 	连接客户端
	 * @param url
	 * @return 200 连接成功
	 * @return 201 存在连接
	 * @return 500 连接失败
	 */
	public static JSONObject connectOpcUaClent(String url) {
		OpcUaClientManager manager = new OpcUaClientManager();
		return manager.bootstrap(url);
	}
	
	
	/**
	 * 	关闭客户端
	 * @param url
	 * @return true关闭成功，false关闭失败
	 */
	public static boolean closeOpcUaClent(String url) {
		if(Client.map.containsKey(url)) {
			List<Object> list = Client.map.get(url);
			OpcUaClient client = (OpcUaClient) list.get(0);
            try {
            	client.disconnect().get();
            	Stack.releaseSharedResources();
            	Client.map.remove(url);
            	logger.info(url+" is close!");
            } catch (Exception e) {
            	logger.error("Error running closeOpcUaClent: {}", e.getMessage(), e);
            	return false;
            }	
		}
		return true;
	}
	/**
	 * 获取根节点信息
	 * @param client
	 * @return
	 */
	public static List<NodeInfo> browseObjectsFolder(OpcUaClient client,String url){
		List<NodeInfo> list = new LinkedList<NodeInfo>();
		try {
            List<ReferenceDescription> nodes = client.getAddressSpace().browse(Identifiers.ObjectsFolder);
            for (ReferenceDescription node : nodes) {
            	NodeInfo NodeInfo = new NodeInfo();
            	int index = node.getNodeId().getNamespaceIndex().intValue();
            	Object Identifier = node.getNodeId().getIdentifier();
            	NodeInfo.setNodeClass(node.getNodeClass().toString());
            	NodeInfo.setIdentifier(Identifier);
            	NodeInfo.setDisplayName(node.getDisplayName().getText());
            	NodeInfo.setNamespaceIndex(index);
            	String nodeClass= node.getNodeClass().toString();
            	NodeInfo.setNodeClass(nodeClass);
            	NodeInfo.setValueRank(1);
            	if(nodeClass.equalsIgnoreCase("Variable")) {
            		NodeId nodeIdchild = OpcUaUtils.getNodeId(index,Identifier);
    				UaVariableNode uaVariableNode = client.getAddressSpace().getVariableNode(nodeIdchild);
    				DataValue dataValue = uaVariableNode.getValue() ;
    				if(dataValue==null) continue;
    				Variant variant  = dataValue.getValue();
    				if(variant.isNull()) continue;
    				ExpandedNodeId expandedNodeId = variant.getDataType().get();
					
    				Object object = variant.getValue();
    				NodeInfo.setNodeType(object.getClass().getSimpleName());
    				if(OpcUaUtils.isBaseObject(object)) {
    					NodeInfo.setNodeType(DataValueTypeEnum.getByCode(expandedNodeId).toString());
    					NodeInfo.setValueRank(-1);
    					NodeInfo.setValue(object);
    					NodeInfo.setAccessLevel(uaVariableNode.getAccessLevel().intValue());
    				}
        		}
            	list.add(NodeInfo);
            }
        } catch (Exception e) {
        	logger.error("Node browseNode error:" + e.getMessage());
        }
		return list;
	}
	
	
	/**
	 * 	查询根节点获取根子节点信息
	 * @param indent 默认为空
	 * @param client
	 * @param browseRoot
	 * @param namespaceIndex
	 */
	public static List<NodeInfo> browseNodeId(OpcUaClient client, Object identifier,int namespaceIndex,String url) {
		List<NodeInfo> list = new ArrayList<NodeInfo>();
		try {
			NodeId nodeId = OpcUaUtils.getNodeId(namespaceIndex,identifier);
			
			List<? extends UaNode> nodes = client.getAddressSpace().browseNodes(nodeId);
        	for (UaNode node : nodes) {
        		NodeInfo nodeInfo = new NodeInfo();
        		String nodeClass = node.getNodeClass().toString();
        		
        		nodeInfo.setValueRank(1);
        		int index = node.getNodeId().getNamespaceIndex().intValue();
            	Object Identifier = node.getNodeId().getIdentifier();
            	
            	if("Variable".equals(nodeClass)) {
            		NodeId nodeIdchild = OpcUaUtils.getNodeId(index,Identifier);
    				UaVariableNode uaVariableNode = client.getAddressSpace().getVariableNode(nodeIdchild);
    				DataValue dataValue = uaVariableNode.getValue() ;
    				if(dataValue==null) continue;
    				Variant variant  = dataValue.getValue();
    				if(variant.isNull()) continue;
    				ExpandedNodeId expandedNodeId = variant.getDataType().get();
					
    				Object object = variant.getValue();
    				nodeInfo.setNodeType(object.getClass().getSimpleName());
    				if(OpcUaUtils.isBaseObject(object)) {
            			nodeInfo.setNodeType(DataValueTypeEnum.getByCode(expandedNodeId).toString());
    					nodeInfo.setValueRank(-1);
    					nodeInfo.setValue(object);
    					nodeInfo.setAccessLevel(uaVariableNode.getAccessLevel().intValue());
    				}
    			}
        		String description = "";
        		LocalizedText localizedText = node.getDescription();
        		if(localizedText!=null) {
        			nodeInfo.setDescription(description);
        		}
        		nodeInfo.setDisplayName(node.getDisplayName().getText());
        		nodeInfo.setIdentifier(Identifier);
        		nodeInfo.setNamespaceIndex(index);
        		nodeInfo.setNodeClass(nodeClass);
            	list.add(nodeInfo);
            }
        } catch (Exception e) {
            logger.error("Browsing ", e);
        }
        return list;
    }
	
	
	/**
	 * 	查询所有节点信息
	 * @param indent 默认为空
	 * @param client
	 * @param browseRoot
	 * @param namespaceIndex
	 */
	public static List<NodeId> browseAllNodeId(OpcUaClient client,String url) {
		List<NodeId> list = new LinkedList<NodeId>();
		try {
            List<ReferenceDescription> nodes = client.getAddressSpace().browse(Identifiers.ObjectsFolder);
            for (ReferenceDescription node : nodes) {
            	int namespaceIndex = node.getNodeId().getNamespaceIndex().intValue();
            	Object identifier = node.getNodeId().getIdentifier();
            	if(namespaceIndex>0) {
            		NodeId nodeId = OpcUaUtils.getNodeId(namespaceIndex,identifier);
            		if(!list.contains(nodeId)) {
            			list.addAll(browseNodeId("",client,nodeId,namespaceIndex));
            		}
            	}
            }
        } catch (Exception e) {
        	logger.error("browseAllNodeId ", e);
        }
		return list;
    }
	
	
	/**
	 * 	浏览节点
	 * @param indent 默认为空
	 * @param client
	 * @param browseRoot
	 * @param namespaceIndex
	 */
	public static List<NodeId> browseNodeId(String indent, OpcUaClient client, NodeId browseRoot,int namespaceIndex) {
		List<NodeId> list = new ArrayList<NodeId>();
		List<Object[]> listObjects = new ArrayList<Object[]>();
		listObjects =  browseNode(indent,client,browseRoot,listObjects,0);
        for(Object[] object : listObjects) {
        	NodeId nodeId = OpcUaUtils.getNodeId((int)object[3], object[0].toString());
        	if(!list.contains(nodeId)) {
        		list.add(nodeId);
    		}
        }
        return list;
    }
	
	
	/**
	 * 	浏览节点
	 * @param indent
	 * @param client
	 * @param browseRoot
	 */
	public static List<Object[]> browseNode(String indent, OpcUaClient client, NodeId browseRoot,List<Object[]> list,int parent) {
		Object[] objects = new Object[4];
		parent++;
        try {
            List<? extends UaNode> nodes = client.getAddressSpace().browseNodes(browseRoot);
            for (UaNode node : nodes) {
                objects[0]=node.getNodeId().getIdentifier();
    			objects[1]=browseRoot.getIdentifier();
    			objects[2]=parent;
    			String nodeClass = node.getNodeClass().toString();
    			if("Variable".equals(nodeClass)) {
    				int index = node.getNodeId().getNamespaceIndex().intValue();
    				objects[3] = index;
            		NodeId nodeIdchild = OpcUaUtils.getNodeId(index,node.getNodeId().getIdentifier());
    				UaVariableNode uaVariableNode = client.getAddressSpace().getVariableNode(nodeIdchild);
    				DataValue dataValue = uaVariableNode.getValue() ;
    				if(dataValue!=null && dataValue.getValue().isNotNull()) {
    					Object object = dataValue.getValue().getValue();
    					if(OpcUaUtils.isBaseObject(object)) {
    						list.add(objects);
        				}
    				}	
    			}
            	objects = new Object[4];
                browseNode(indent + "  ", client, node.getNodeId(),list,parent);
            }
        } catch (UaException e) {
            logger.error("Browsing nodeId={} failed: {}", browseRoot, e.getMessage(), e);
        }
        return list;
    }
	
	
	public static int countNode(OpcUaClient client, NodeId browseRoot) {
		int count = 0;
		try {
            List<? extends UaNode> nodes = client.getAddressSpace().browseNodes(browseRoot);
            if(nodes!=null && nodes.size()>0) 
            	return nodes.size();
        } catch (UaException e) {
            logger.error("Browsing nodeId={} failed: {}", browseRoot, e.getMessage(), e);
        }
        return count;
	}
	
	/**
	 * 	获取单个节点值
	 * @param client
	 * @param nodeId
	 */
	public static Object readValue(OpcUaClient client,NodeId nodeId){
		/*
		 * 第一个参数如果设置为0的话会获取最新的值，如果maxAge设置到Int32的最大值，则尝试从缓存中读取值。
		 * 第二个参数为请求返回的时间戳,
		 * 第三个参数为要读取的NodeId对象。
		 */
	    DataValue value;
		try {
			value = client.readValue(0.0, TimestampsToReturn.Both, nodeId).get();
			return value.getValue().getValue();
		} catch (InterruptedException e) {
			
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		return null;
	    
	}
	
	/**
	 * 	获取多个节点值
	 * @param client
	 * @param nodeId
	 */
	public static Map<NodeId, Object> readValues(OpcUaClient client,List<NodeId> nodeIds){
		Map<NodeId, Object> map = new HashMap<NodeId, Object>();
		CompletableFuture<List<DataValue>> providersResponse = client.readValues(0.0, TimestampsToReturn.Both, nodeIds);
		List<DataValue> list =  providersResponse.join();
		for(int i=0;i< list.size();i++) {
			DataValue dataValue = list.get(i);
			Object value = dataValue.getValue().getValue();
			if (value instanceof LocalizedText || value instanceof UInteger) {
				continue;
			}
			map.put(nodeIds.get(i), value);
		}
		return map;
	}
	
    
	/**
	 * 	写变量
	 * @param client
	 * @param map
	 * @param nameSpaceIndex
	 * @return
	 */
	public static boolean writeValue(OpcUaClient client,Map<String, Object> map,int nameSpaceIndex){
		if(map==null || map.size()==0) 
			return true;
		for(Map.Entry<String, Object> entry:map.entrySet()){  
	        String key = entry.getKey();
	        Object value = entry.getValue();  
	        Variant v = new Variant(value);
		    DataValue dataValue = new DataValue(v,null,null);
		    NodeId nodeId = new NodeId(nameSpaceIndex, key);
		    StatusCode statusCode;
			try {
				statusCode = client.writeValue(nodeId,dataValue).get();
				logger.info("writeValue: key="+key+",value="+value+",statusCode="+statusCode.getValue());
				boolean result = statusCode.isGood();
				if(!result) {
					return result;
				}
			}catch (Exception e) {
				logger.error("Error writeValue: {}", e.getMessage(), e);
				return false;
			}
		    
	    }  
	    return true;
	}
	
	
    /**
     * 	订阅
     * @param client
     * @param nodeIds  创建订阅的变量
     * @param sf 订阅间隔，单位ms
     * @throws InterruptedException
     * @throws ExecutionException
     */
	public static void createSubscription(String url,OpcUaClient client,List<NodeId> nodeIds, double sf){
		/*
		 * 创建订阅，添加受监视的项，然后等待值到达。
		 * 服务器断线重连，应该调用onSubscriptionTransferFailed（）回调，
		 * 因为客户端重新连接服务器将无法在订阅丢失其所有状态后传输订阅。
		 */
		try {
	        client.getSubscriptionManager().addSubscriptionListener(new org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscriptionManager.SubscriptionListener() {
	            @Override
	            public void onSubscriptionTransferFailed(UaSubscription subscription, StatusCode statusCode) {
	                Stack.sharedExecutor().execute(() -> {
	                    try {
	                        createItemAndWait(url,client,nodeIds,sf);
	                    } catch (InterruptedException | ExecutionException e) {
	                        logger.error("Error creating Subscription: {}", e.getMessage(), e);
	                    }
	                });
	            }
	        });
	        createItemAndWait(url,client,nodeIds,sf);
            
		} catch (InterruptedException | ExecutionException e) {
        	logger.info("订阅点位时发生了错误", e);
        }
	}
	
	
	private static void createItemAndWait(
			String url,
			OpcUaClient client,
			List<NodeId> nodeIds,
			double sf) throws InterruptedException, ExecutionException {

		//创建发布间隔1000ms的订阅对象
	    UaSubscription subscription = client.getSubscriptionManager().createSubscription(sf).get();
	    List<MonitoredItemCreateRequest> requests = new ArrayList<>();
        for (NodeId nodeId : nodeIds) {
            ReadValueId readValueId = new ReadValueId(
                    nodeId, AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE
            );
            UInteger clientHandle = subscription.nextClientHandle();
            //	创建监控的参数
            MonitoringParameters parameters = new MonitoringParameters(
                    clientHandle, sf, null, UInteger.valueOf(10), true
            );
            //	创建监控项请求
		    //	该请求最后用于创建订阅。
            MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(
                    readValueId, MonitoringMode.Reporting, parameters
            );
            requests.add(request);
        }
	    
	    //	创建监控项，并且注册变量值改变时候的回调函数。
	    subscription.createMonitoredItems(
	    TimestampsToReturn.Both,
	    requests,
	    (item,id)->{
	        item.setValueConsumer((item1, value)->{
	        	NodeId nodeId = item1.getReadValueId().getNodeId();
	        	Variant  variant = value.getValue();
	        	String identifier = String.valueOf(nodeId.getIdentifier());
	        	JSONObject subValue = OpcUaDataCache.subData.get(url);
	        	if(subValue == null) {
	        		subValue = new JSONObject();
	        	}
	        	subValue.put("ns="+nodeId.getNamespaceIndex()+";"+identifier.replace(".", "#"), variant.getValue());
	        	OpcUaDataCache.subData.put(url, subValue);
	        });
	    }).get();    
	}


	
}
